#include <iostream>
#include "get_arg.h"
#include <vector>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <boost/lexical_cast.hpp>
#include <boost/tuple/tuple.hpp>

#include "../../components/perlutil.h"
#include "../../components/digest.h"
#include "../../components/md4.h"
#include "../../components/progress_indicator.h"
#include "../../components/bcpp_parsefasta.h"
#include "../../components/greta/regexpr2.h"
#include "../../components/bcpp_parsefasta.h"
#define _MY_STL_MACROS
#include "../../components/stlhelper.h"
#include "../../components/portable_timer.h"
#include "../../components/pg_db_cursor.h"
#include "../../components/pg_copy_guard.h"
using std::string;


using std::vector;
using std::string;
using std::ifstream;
using std::ofstream;
using std::cerr;
using std::sort;
using boost::lexical_cast;
using boost::tie;
using std::equal_range;

typedef regex::match_results::backref_type regex_bref;


//*****************************************************************************
// for testing for non-digit characters
//*****************************************************************************
namespace{
	char non_digit_buf [256];

	char* init_non_digit_buf()
	{
		std::fill(non_digit_buf,  non_digit_buf + 256, 1);
		for (char c = '0'; c <= '9'; ++c)
			non_digit_buf[c] = '\0';
		return non_digit_buf;
	}
}
struct op_test_non_digit
{
	static bool* pbuf;
	bool operator ()(char c) const { return pbuf[c];}
};
char* op_test_non_digit::pbuf = init_non_digit_buf();


//88888888888888888888888888888888888888888888888888888888888888888888888888888888

//	get_gi_to_taxid

//88888888888888888888888888888888888888888888888888888888888888888888888888888888
bool get_gi_to_taxid(const std::string& file_taxa_id, bool verbose)
{
	t_portable_timer timer;
	ostringstream ignore_strm;
	ostream& verbose_strm = verbose ? cerr : ignore_strm;
	verbose_strm << "\tReading gi => taxid pairs...\n";
	ifstream ifs(file_taxa_id.c_str());
	if (!ifs.good())
	{
		verbose_strm << "Error:\n\tCould not open the file [" << file_taxa_id << "]\n";
		return false;
	}

	unsigned cnt_gi_to_taxid = 0;
	{
		t_progress_indicator dots(verbose_strm, 40000);
		t_pg_copy_guard copy_buffer(pg_db, "gi_to_taxid", verbose_strm);
		if (!copy_buffer)
			return false;
		string gi, taxid;
		while (getline(ifs, gi, '\t') && getline(ifs, taxid, '\n'))
		{
			if (includes_if(gi, op_test_non_digit()) ||
				includes_if(taxid, op_test_non_digit()))
				continue;
			copy_buffer += gi;
			copy_buffer += '\t';
			copy_buffer += taxid;
			copy_buffer += '\n';
			copy_buffer.cached_write();
			cnt_gi_to_taxid++;
			dots++;
		}
		if (!copy_buffer.end_copy())
			return false;
	}
	if (verbose)
	{
		cerr << "\t" << cnt_gi_to_taxid << "\tgi => taxid pairs read.\n";
		cerr << "\tParsing gi=>TaxID links from NCBI took " << timer << "\n";
	}

	return true;
}


//88888888888888888888888888888888888888888888888888888888888888888888888888888888

//	get_name_digest_to_taxid

//88888888888888888888888888888888888888888888888888888888888888888888888888888888
bool get_name_digest_to_taxid(const std::string& file_taxa_names, bool verbose)
{
	t_portable_timer timer;
	if (verbose)
		cerr << "\tReading name => taxid pairs...\n";
	ifstream ifs(file_taxa_names.c_str());
	if (!ifs.good())
	{
		if (verbose)
			cerr << "Error:\n\tCould not open the file [" << file_taxa_names << "]\n";
		return false;
	}


	unsigned cnt_names_to_taxid = 0;
	{
		ostringstream os;
		t_progress_indicator dots(verbose ? cerr : os, 8000);
		t_pg_copy_guard copy_buffer(pg_db, "taxname_to_taxid", verbose_strm);
		if (!copy_buffer)
			return false;
		string line;
		while (getline(ifs, line))
		{
			dots++;
			vector<string> ret;
			perl_split(line,  "\t|\t", ret, true,  3);
			if (ret.size() != 3)
			{
                ifs >> line;
				continue;
			}
			// taxid must be positive number
			if (includes_if(ret[0], op_test_non_digit()))
				continue;
			t_digest d;
			md4_hash(ret[1], d);

			copy_buffer += d;
			copy_buffer += '\t';
			copy_buffer += ret[0];
			copy_buffer += '\n';
			copy_buffer.cached_write();
			cnt_names_to_taxid++;
		}
		if (!copy_buffer.end_copy())
			return false;
	}

	if (verbose)
	{
		cerr << "\t" << cnt_names_to_taxid << "\tname => taxid pairs read.\n";
		cerr << "\tParsing Taxa names from NCBI took " << timer << "\n";
	}
	return true;
}


int main (int argc, char *argv[])
{

	// don't use C streams
	std::ios::sync_with_stdio(false);
	std::cin.tie (0);

	// get command line arguments
	t_program_args args;
	if (!GetArguments(argc, argv, args))
		return 1;

	//____________________________________________________________________________________
	// 		Connect to PostgreSQL
	//____________________________________________________________________________________
	t_pg_cursor pg_db(args.db_login.c_str(), "panda_cursor");	//"dbname=test user=postgres"
	if ( pg_db.ConnectionBad() )
	{
		args.CERR().strm()	<< "\nERROR!! Connection to database ["
							<< args.db_login << "] failed.\n"
							<< pg_db.ErrorMessage() << "\n\n";
		return 1;
	}

	//____________________________________________________________________________________
	// 		Open error file
	//____________________________________________________________________________________
	ofstream f_invalid(args.file_error.c_str());
	if (!f_invalid.good())
	{
		if (args.verbose)
			cerr << "Error:\n\tCould not open the file [" << args.file_error << "]\n";
		return 1;
	}

	//____________________________________________________________________________________
	// 		Open taxa files
	//____________________________________________________________________________________
	std::vector<t_digest_taxid> digest_to_taxid;
	std::vector<t_gi_taxid> gi_to_taxid;
	if (!get_name_digest_to_taxid(args.file_taxa_names, digest_to_taxid, args.verbose))
		return 1;
	if (!get_gi_to_taxid(args.file_taxa_id, gi_to_taxid, args.verbose))
		return 1;

	{
		t_portable_timer timer;
		ostringstream os;
		t_progress_indicator dots(args.verbose ? cerr : os, 10000);
		t_process_sequence seq_data(args.regex_gi, digest_to_taxid, gi_to_taxid, f_invalid, dots);
		BioCPP::Process_FA_Sequences(cin, seq_data);
		dots.finish();
		if (args.verbose)
		{
			cerr << seq_data.count_seq << "\tsequences were written out\n";
			cerr << seq_data.count_invalid << "\tsequences had accessions with unresolved taxonomy\n";
			cerr << seq_data.count_name_matches << "\ttaxIDs could only be resolved via their names\n";
			cerr << "Writing new sequence file took " << timer << "\n";
		}
	}
}

